package main

import (
	"fmt"
	"net"
	"os"
	"strconv"
	"time"
)

const Info = `
==================================== 操作方法 ===========================================
钱包操作:
 getPubKey      --nodeID 节点                                     "获取节点的公钥"
 getPrivateKey  --nodeID 节点                                     "获取节点的私钥"
 getAllPubKey   --nodeID 节点                                     "获取所有节点的公钥"
 getUserInfo    --nodeID 节点  --nodeID 节点编号                  "查看区块链上的账户信息"
交易操作:
 sendNorTrans   --nodeID 节点  --from 转账人 --to 接收人 --value 金额  "普通交易"
 sendGeneTrans  --nodeID 节点  --from 转账人 --to 接受人 --data  数据  "分发交易"
 sendTransTrans --nodeID 节点  --from 转账人 --to 接受人 --data  数据  "转移交易"
查看区块链:
 printBlockInfo --nodeID 节点 --round 轮次                        "查看区块数据"
 printTransPool --nodeID 节点                                     "查看交易池"
 printTransNum  --nodeID 节点                                     "查看每轮已发生交易数"
 printTransAll  --nodeID 节点                                     "查看系统中发生的交易数"
 printMiniPool  --nodeID 节点                                     "查看微块池"
 printHeight    --nodeID 节点                                     "查看区块高度"
实验模拟:
 transSpeed           --round 轮次  --txs 交易数            "模拟交易速度(委员会数目)"
==========================================================================================
`

////////////////////////////////
// 系统参数: 与服务端对应
////////////////////////////////
const nodeNumber = 20       // 系统中总结点数【以server参数一一对应】
const nodeStartNum = "10000" // 节点的启动号【以server参数一一对应】

var nodeAddrs []string = make([]string, nodeNumber) // 存放系统中节点的ID编号

func init() {
	nodeDigit, _ := strconv.Atoi(nodeStartNum)

	for i := 0; i < nodeNumber; i++ {
		nodeAddr := strconv.Itoa(nodeDigit + i)
		nodeAddrs[i] = nodeAddr
	}
}

func main() {
	// 获取客户端参数
	args := os.Args // go run client.go getPubKey --nodeID 8000

	// 判断
	if len(args) < 2 {
		fmt.Println(Info)
		return
	}

	// 分析命令
	cmd := args[1]

	switch cmd {
	case "getPubKey":
		// 标识: d
		getPubKey(args)
	case "getPrivateKey":
		// 标识: d
		getPrivateKey(args)
	case "getAllPubKey":
		getAllPubKey(args)
	case "sendNorTrans":
		// 标识: a
		sendNorTrans(args)
	case "sendGeneTrans":
		// 标识: b
	case "sendTransTrans":
		// 标识: c
	case "printBlockInfo":
		// 标识: d
		printBlockInfo(args)
	case "printTransPool":
		printTransPool(args)
	case "printTransNum":
		printTransNum(args)
	case "printTransAll":
		printTransAll(args)
	case "printMiniPool":
		printMiniPool(args)
	case "printHeight":
		printHeight(args)
	case "getUserInfo":
		getUserInfo(args)
	case "transSpeed":
		transSpeedBlockTest(args)
	default:
		fmt.Print("无效的命令，请重新输入...")
		fmt.Printf(Info)
	}
}

// 1 获取节点对应的公钥
// go run client.go getPubKey --nodeID 8000
func getPubKey(args []string) {
	// 组织数据，按照规则进行拆分
	// 字符串拼接: 使用 _ 进行划分
	data := "d" + "_" + "getPubKey" + "_" + args[3] // 拼接字符串 d_getPubKey_8000

	// 主动发送连接请求
	conn, err := net.Dial("tcp", "localhost:"+args[3])

	if err != nil {
		fmt.Println("Dial err", err)
	}

	defer conn.Close() // 客户端终止时，关闭于服务器通讯的socket

	// 将命令行中的数据，发送给客户端
	_, err = conn.Write([]byte(data))
	if err != nil {
		fmt.Println("conn.Write err:", err)
		return
	}
}

// 2 获取节点对应的私钥
func getPrivateKey(args []string) {
	// 组织数据，按照规则进行拆分
	// 字符串拼接: 使用 _ 进行划分
	data := "d" + "_" + "getPrivateKey" + "_" + args[3] // 拼接字符串 d_getPubKey_8000

	// 主动发送连接请求
	conn, err := net.Dial("tcp", "localhost:"+args[3])

	if err != nil {
		fmt.Println("Dial err", err)
	}

	defer conn.Close() // 客户端终止时，关闭于服务器通讯的socket

	// 将命令行中的数据，发送给客户端
	_, err = conn.Write([]byte(data))
	if err != nil {
		fmt.Println("conn.Write err:", err)
		return
	}
}

// 3 获取所有节点的公钥
func getAllPubKey(args []string) {
	// 组织数据，按照规则进行拆分
	// 字符串拼接: 使用 _ 进行划分
	data := "d" + "_" + "getAllPubKey" + "_" + args[3] // 拼接字符串 d_getPubKey_8000

	// 主动发送连接请求
	conn, err := net.Dial("tcp", "localhost:"+args[3])

	if err != nil {
		fmt.Println("Dial err", err)
	}

	defer conn.Close() // 客户端终止时，关闭于服务器通讯的socket

	// 将命令行中的数据，发送给客户端
	_, err = conn.Write([]byte(data))
	if err != nil {
		fmt.Println("conn.Write err:", err)
		return
	}
}

// 4 打印区块链信息// TODO 仅支持创世区块
// printBlockInfo --nodeID 节点 --round 轮次
func printBlockInfo(args []string) {
	// 组织数据，按照规则进行拆分
	// 字符串拼接: 使用 _ 进行划分

	// 拼接字符串 d_printBlockInfo_8000_0
	data := "d" + "_" + "printBlockInfo" + "_" + args[3] + "_" + args[5]

	// 主动发送连接请求
	conn, err := net.Dial("tcp", "localhost:"+args[3])

	if err != nil {
		fmt.Println("Dial err", err)
	}

	defer conn.Close() // 客户端终止时，关闭于服务器通讯的socket

	// 将命令行中的数据，发送给客户端
	_, err = conn.Write([]byte(data))
	if err != nil {
		fmt.Println("conn.Write err:", err)
		return
	}
}

// 正常交易
func sendNorTrans(args []string) {
	// sendNorTrans   --nodeID 节点  --from 转账人 --to 接收人 --value 金额
	// 拼接字符串
	data := "a" + "_" + args[5] + "_" + args[7] + "_" + args[9]
	//      "a"         "from"           "to"          "value"

	// 主动发送连接请求
	conn, err := net.Dial("tcp", "localhost:"+args[3])

	if err != nil {
		fmt.Println("Dial err", err)
	}

	defer conn.Close() // 客户端终止时，关闭于服务器通讯的socket

	// 将命令行中的数据，发送给客户端
	_, err = conn.Write([]byte(data))
	if err != nil {
		fmt.Println("conn.Write err:", err)
		return
	}
}

// 查看交易池
func printTransPool(args []string) {
	data := "d" + "_" + "printTransPool" + "_" + args[3] // 拼接字符串 d_printTransPool_8000

	// 主动发送连接请求
	conn, err := net.Dial("tcp", "localhost:"+args[3])

	if err != nil {
		fmt.Println("Dial err", err)
	}

	defer conn.Close() // 客户端终止时，关闭于服务器通讯的socket

	// 将命令行中的数据，发送给客户端
	_, err = conn.Write([]byte(data))
	if err != nil {
		fmt.Println("conn.Write err:", err)
		return
	}
}

// 查看全局交易数目
func printTransNum(args []string) {
	data := "d" + "_" + "printTransNum" + "_" + args[3] // 拼接字符串 d_printTransNum_8000

	// 主动发送连接请求
	conn, err := net.Dial("tcp", "localhost:"+args[3])

	if err != nil {
		fmt.Println("Dial err", err)
	}

	defer conn.Close() // 客户端终止时，关闭于服务器通讯的socket

	// 将命令行中的数据，发送给客户端
	_, err = conn.Write([]byte(data))
	if err != nil {
		fmt.Println("conn.Write err:", err)
		return
	}
}

// 查看系统中发生的总交易数目
func printTransAll(args []string) {
	data := "d" + "_" + "printTransAll" + "_" + args[3] // 拼接字符串 d_printTransNum_8000

	// 主动发送连接请求
	conn, err := net.Dial("tcp", "localhost:"+args[3])

	if err != nil {
		fmt.Println("Dial err", err)
	}

	defer conn.Close() // 客户端终止时，关闭于服务器通讯的socket

	// 将命令行中的数据，发送给客户端
	_, err = conn.Write([]byte(data))
	if err != nil {
		fmt.Println("conn.Write err:", err)
		return
	}
}

// 查看微块池
func printMiniPool(args []string) {
	data := "d" + "_" + "printMiniPool" + "_" + args[3] // 拼接字符串 d_printTransPool_8000

	// 主动发送连接请求
	conn, err := net.Dial("tcp", "localhost:"+args[3])

	if err != nil {
		fmt.Println("Dial err", err)
	}

	defer conn.Close() // 客户端终止时，关闭于服务器通讯的socket

	// 将命令行中的数据，发送给客户端
	_, err = conn.Write([]byte(data))
	if err != nil {
		fmt.Println("conn.Write err:", err)
		return
	}
}

// 查看区块高度
func printHeight(args []string) {
	// 组织数据，按照规则进行拆分
	// 字符串拼接: 使用 _ 进行划分
	data := "d" + "_" + "printHeight" + "_" + args[3] // 拼接字符串 d_printHeight_8000

	// 主动发送连接请求
	conn, err := net.Dial("tcp", "localhost:"+args[3])

	if err != nil {
		fmt.Println("Dial err", err)
	}

	defer conn.Close() // 客户端终止时，关闭于服务器通讯的socket

	// 将命令行中的数据，发送给客户端
	_, err = conn.Write([]byte(data))
	if err != nil {
		fmt.Println("conn.Write err:", err)
		return
	}
}

// 查看用户信息
func getUserInfo(args []string) {
	// 字符串拼接: 使用 _ 进行划分
	data := "d" + "_" + "getUserInfo" + "_" + args[3] + "_" + args[5] // 拼接字符串 d_getUserInfo_8000_公钥

	// 主动发送连接请求
	conn, err := net.Dial("tcp", "localhost:"+args[3])

	if err != nil {
		fmt.Println("Dial err", err)
	}

	defer conn.Close() // 客户端终止时，关闭于服务器通讯的socket

	// 将命令行中的数据，发送给客户端
	_, err = conn.Write([]byte(data))
	if err != nil {
		fmt.Println("conn.Write err:", err)
		return
	}
}

func transSpeedBlockTest(args []string) {
	// go run client.go transSpeed --round 10  --txs 4000
	//                   交易测试       轮数   每轮的最大交易数目【与config匹配】
	round, _ := strconv.Atoi(args[3]) // 获取轮数
	txs, _ := strconv.Atoi(args[5])   // 每轮的交易数

	value := 0

	for i := 1; i <= round; i++ {
		for j := 1; j <= txs; j++ {

			value += 1

			data := "a" + "_" + // "标识符"
				"10001_" + // "from"
				"10002_" + // "to"
				strconv.Itoa(value) // "value"

			conn, _ := net.Dial("tcp", "localhost:10000")  

			_, err := conn.Write([]byte(data))
			if err != nil {
				fmt.Println("conn.Write err:", err)
				return
			}

			conn.Close()

			time.Sleep(1000 * time.Millisecond) // 暂停
		}

		time.Sleep(20 * time.Second)
	}
}
